home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / KJPYLINT.PY < prev    next >
Encoding:
Python Source  |  1999-07-28  |  15.8 KB  |  601 lines

  1. #!/usr/local/bin/python
  2. """python lint using kwParsing
  3.  
  4. The goal of this module/filter is to help find
  5. programming errors in python source files.
  6.  
  7. As a filter use thusly:
  8.  
  9. % python kjpylint.py source_file.py
  10.  
  11. As an internal tool use like this:
  12.  
  13.   import kjpylint
  14.   (pyg, context) = kjpylint.setup()
  15.   kjpylint.lint(data, pyg, context)
  16.  
  17. where data is the text of a python program.
  18. You can build your own context structure by
  19. subclassing GlobalContext, and redefining
  20. GlobalContext.complain(string) for example.
  21. You could do a lot more than that too...
  22.  
  23. Also, to lint all *.py files recursively contained
  24. in a directory hierarchy use
  25.  
  26.   kjpylint.lintdir("/usr/local/lib/python") # for example
  27.  
  28. FEATURES:
  29.  
  30. Lint expects
  31.   1) a newline or two at the end of the data;
  32.   2) consistent indenting (and inconsistency may be invisible)
  33.      [eg " \t" and "\t" are not the same indent
  34.      to Lint, but Python sees them the same.]
  35.  
  36. If (1) or (2) are not satisfied Lint will raise
  37. an exception.
  38.  
  39. Buglets: lambdas and for loops on one line generate
  40.   extraneous warnings.
  41.  
  42. Notes:
  43. ======
  44. The lint process works, in outline, like this.
  45. Scan over a python program 
  46.  
  47. x = 1
  48.  
  49. def f(a):
  50.     a = x
  51.     d.x, y = b
  52.  
  53. z = w
  54.  
  55. and build annotations like
  56.  
  57. [ set("x", 1),
  58.   [
  59.     get("x", 4)
  60.     set("a", 4)
  61.     get("b", 5)
  62.     get("d", 5)
  63.     set("y", 5)
  64.     pop_local()
  65.   ]
  66.   get("w", 7)
  67.   set("z", 7) ]
  68.  
  69. from this stream conclude
  70.   warning on line 5: b used before set
  71.   warning on line 5: d used before set
  72.   warning on line 5: y set, never used
  73. etc. using simple one pass approximate flow
  74. analysis.
  75. """
  76.  
  77. pyg = context = None
  78.  
  79. #import pygram
  80. from pygram import newlineresult
  81.  
  82. # reduction rules:
  83. #  only need to consider 
  84. #    expressions, assignments, def, class, global, import, from, for
  85. #
  86. # expressions return a list of unqualified names, not known set
  87. # qualified names are automatically put in context as refs
  88. #
  89. # assignments set left names, ref right names
  90. #
  91. # def sets new name for function and args,
  92. #  refs other names
  93. #
  94. # class adds new name for class
  95. #  refs other names
  96. #
  97. # global forces global interpretation for name
  98. #
  99. # import adds FIRST names
  100. # from sets names
  101. # for sets names
  102. #
  103. # related rules
  104. # ASSIGNMENT REQUIRES SPECIAL TREATMENT
  105. #@R assn1 :: assn >> testlist = testlist
  106.  
  107. def assn1(list, context):
  108.     [t1, e, t2] = list
  109.     return assn(t1, t2)
  110.  
  111. #@R assnn :: assn >> testlist = assn
  112.  
  113. def assnn(list, context):
  114.     [t1, e, a1] = list
  115.     return assn(t1, a1)
  116.  
  117. # @R assn1c :: assn >> testlist , = testlist
  118. def assn1c(list, context):
  119.     [t1, c, e, t2] = list
  120.     return assn(t1, t2)
  121.  
  122. # @R assn1c2 :: assn >> testlist , = testlist ,
  123. def assn1c2(list, context):
  124.     del list[-1]
  125.     return assn1c(list, context)
  126.  
  127. # @R assnnc :: assn >> testlist , = assn
  128. def assnnc(list, context):
  129.     return assn1c(list, context)
  130.  
  131. def assn(left, right):
  132.     result = right
  133.     for x in left:
  134.         (ln, ri, op, name) = x
  135.         if op == "ref":
  136.            result.append( (ln, ri, "set", name) )
  137.         else:
  138.            result.append(x)
  139.     return result
  140.  
  141. #@R except2 :: except_clause >> except test , test
  142. def except2(list, context):
  143.     [e, t1, c, t2] = list
  144.     result = t1
  145.     for (ln, ri, op, name) in t2:
  146.         result.append( (ln, ri, "set", name) )
  147.     return result
  148.  
  149. #@R smassn :: small_stmt >> assn
  150. #  ignored
  151.  
  152. #@R rfrom :: import_stmt >> from dotted_name import name_list 
  153. #@R rfromc :: import_stmt >> from dotted_name import name_list ,
  154.  
  155. def rfrom(list, context):
  156.     #print rfrom, list
  157.     [f, d, i, n] = list
  158.     # ignore d
  159.     return n
  160.  
  161. def rfromc(list, context):
  162.     return rfrom(list[:-1])
  163.  
  164. def mark(kind, thing, context):
  165.     L = context.LexD
  166.     lineno = L.lineno
  167.     # are we reducing on a newline?
  168.     if L.lastresult==newlineresult:
  169.        lineno = lineno-1
  170.     return (lineno, -L.realindex, kind, thing)
  171.  
  172. #@R dn1 :: dotted_name >> NAME
  173.  
  174. def dn1(list, context):
  175.     #print "dn1", list
  176.     #L = context.LexD
  177.     return [ mark("set", list[0], context) ]
  178.     #return [ (L.lineno, -L.realindex, "set", list[0]) ]
  179.  
  180.  
  181.  
  182. #  handles import case, make name set local
  183. #@R nlistn :: name_list >> name_list  , NAME
  184.  
  185. def nlistn(list, context):
  186.     #print "nlistn", list
  187.     [nl, c, n] = list
  188.     #L = context.LexD
  189.     #nl.append( (L.lineno, -L.realindex, "set", n) )
  190.     nl.append( mark("set", n, context) )
  191.     return nl
  192.  
  193. #@R nlist1 :: name_list >> NAME
  194.  
  195. def nlist1(list, context):
  196.     #print "nlist1", list
  197.     #L = context.LexD
  198.     #return [ (L.lineno, -L.realindex, "set", list[0]) ]
  199.     return [ mark("set", list[0], context) ]
  200.  
  201. # ignore lhs in calls with keywords.
  202. #@R namearg :: argument >> test = test
  203. def namearg(list, context):
  204.     [t1, e, t2] = list
  205.     return t2
  206.  
  207. #  handles from case, make names set local
  208. #@R global1 :: global_stmt >> global NAME 
  209.  
  210. def global1(list, context):
  211.     #print "global1", list
  212.     #L = context.LexD
  213.     #return [ (L.lineno, -L.realindex, "global", list[1]) ]
  214.     return [ mark("global", list[1], context) ]
  215.  
  216. #@R globaln :: global_stmt >> global_stmt , NAME 
  217. #  handles global, make names global (not set or reffed)
  218.  
  219. def globaln(list, context):
  220.     #print "globaln", list
  221.     [g, c, n] = list
  222.     #L = context.LexD
  223.     #g.append( (L.lineno, -L.realindex, "global", n) )
  224.     g.append( mark("global", n, context) )
  225.     return g
  226.  
  227. #@R for1 :: for_stmt >> 
  228. #for exprlist in testlist  : 
  229. #     suite
  230.  
  231. def for1(list, context):
  232.     #print "for1", list
  233.     [f, e, i, t, c, s] = list
  234.     refs = t + s
  235.     return assn(e, refs)
  236.  
  237. #@R for2 :: for_stmt >> 
  238. #for exprlist in testlist  : 
  239. #     suite 
  240. #else : 
  241. #     suite
  242.  
  243. def for2(list,context):
  244.     #print "for2", list
  245.     [f, e, i, t, c1, s1, el, c2, s2] = list
  246.     refs = t + s1 + s2
  247.     return assn(e, refs)
  248.  
  249. ###
  250. #@R class1 :: classdef  >> class NAME : suite
  251. def class1(list, context):
  252.     [c, n, cl, s] = list
  253.     return Class(n, [], s, context)
  254.  
  255. #@R class2 :: classdef  >> class NAME ( testlist ) : suite
  256. def class2(list, context):
  257.     [c, n, opn, t, cls, cl, s] = list
  258.     return Class(n, t, s, context)
  259.  
  260. def Class(name, testlist, suite, context):
  261.     globals = analyse_scope(name, suite, context, unused_ok=1)
  262.     context.defer_globals(globals)
  263.     result = testlist
  264.     L = context.LexD
  265.     # try to correct lineno
  266.     lineno = L.lineno
  267.     realindex = L.realindex
  268.     for (ln, ri, op, n) in testlist+suite:
  269.         lineno = min(lineno, ln)
  270.     result.append((lineno, -realindex, "set", name))
  271.     #result.append( mark("set", name, context) )
  272.     # supress complaints about unreffed classes
  273.     result.append((lineno+1, -realindex, "qref", name))
  274.     #result.append( mark("qref", name, context) )
  275.     return result
  276.  
  277. # vararsglist requires special treatment.
  278. #  return (innerscope, outerscope) pair of lists
  279. # @R params1 :: parameters >> ( varargslist )
  280. def params1(l, c):
  281.     return l[1]
  282.  
  283. params1c = params1
  284.  
  285. #@R params2 :: varargslist >> 
  286. def params2(l, c):
  287.     return ([], [])
  288.  
  289. #@R params3 :: varargslist >> arg
  290. def params3(l, c):
  291.     return l[0]
  292.  
  293. #@R params4 :: varargslist >> varargslist , arg
  294. def params4(l, c):
  295.     #print "params4", l
  296.     [v, c, a] = l
  297.     v[0][0:0] = a[0]
  298.     v[1][0:0] = a[1]
  299.     return v
  300.  
  301. #@R argd :: arg >> NAME = test
  302. def argd(l, c):
  303.     [n, e, t] = l
  304.     #L = c.LexD
  305.     #return ([(L.lineno, -L.realindex, "set", n)], t)
  306.     return ([ mark("set", n, c) ], t)
  307.  
  308. #@R arg2 :: arg >> fpdef
  309. def arg2(l, c):
  310.     return l[0]
  311.  
  312. #@R arg3 :: arg >> * NAME
  313. def arg3(l, c):
  314.     del l[0]
  315.     return fpdef1(l, c)
  316.  
  317. #@R arg4 :: arg >> ** NAME
  318. def arg4(l, c):
  319.     #del l[0]
  320.     return arg3(l, c)
  321.  
  322. #@R fpdef1 :: fpdef  >> NAME
  323. def fpdef1(l, c):
  324.     [n] = l
  325.     #LexD = c.LexD
  326.     return ([ mark("set", n, c) ], [])
  327.  
  328. #@R fpdef2 :: fpdef  >>  ( fplist )
  329. def fpdef2(l, c):
  330.     return l[1]
  331.  
  332. ## @R fpdef2c :: fpdef  >>  ( fplist , )
  333. #fpdef2c = fpdef2
  334.  
  335. ##31
  336. #@R fplist1 :: fplist >> fpdef
  337. def fplist1(l, c):
  338.     #print l
  339.     return l[0]
  340.  
  341. #@R fplistn :: fplist >> fplist , fpdef
  342. fplistn = params4
  343.  
  344. #@R rdef :: funcdef >> def NAME parameters : suite
  345. def rdef(list, context):
  346.     #print "rdef", list
  347.     [ddef, name, parameters, c, suite] = list
  348.     (l, g) = parameters
  349.     globals = analyse_scope(name, l + suite, context)
  350.     # for embedded function defs global internal refs must be deferred.
  351.     context.defer_globals(globals)
  352.     result = g
  353.     L = context.LexD
  354.     # try to steal a lineno from other declarations:
  355.     lineno = L.lineno
  356.     index = L.realindex
  357.     for (ln, ri, op, n) in l+g+suite:
  358.         lineno = min(lineno, ln)
  359.     if name is not None:
  360.        result.append((lineno, -index, "set", name))
  361.        # Note: this is to prevent complaints about unreffed functions
  362.        result.append((lineno+1, -index, "qref", name))
  363.     return result
  364.  
  365. #@R testlambda1 :: test >> lambda varargslist : test
  366. def testlambda1(list, context):
  367.     [l, v, c, t] = list
  368.     return rdef(["def", None, v, ":", t], context)
  369.  
  370. def analyse_scope(sname, var_accesses, context, unused_ok=0):
  371.     var_accesses.sort()
  372.     result = []
  373.     globals = {}
  374.     locals = {}
  375.     # scan for globals
  376.     for x in var_accesses:
  377.         (ln, ri, op, name) = x
  378.         if op == "global":
  379.            globals[name] = ln
  380.         #result.append(x) (ignore global sets in local context)
  381.     # scan for locals
  382.     for (ln, ri, op, name) in var_accesses:
  383.         if op == "set" and not locals.has_key(name):
  384.            if globals.has_key(name):
  385.               context.complain(
  386.      "Warning: set of global %s in local context %s" % (`name`, `sname`))
  387.               result.append( (ln, ri, op, name) )
  388.               pass # ignore global set in local context
  389.            else:
  390.               locals[name] = [ln, 0] # line assigned, #refs
  391.     # scan for use before assign, etc.
  392.     for x in var_accesses:
  393.         (ln, ri, op, name) = x
  394.         if locals.has_key(name):
  395.            if op in ["ref", "qref"]:
  396.               set = locals[name]
  397.               set[1] = set[1] + 1
  398.               assnln = set[0]
  399.               if (ln <= assnln):
  400.                  context.complain( 
  401.           "(%s) local %s ref at %s before assign at %s" % (
  402.            sname, `name`, ln, `assnln`))
  403.         elif op not in ("global", "set"):
  404.            # ignore global sets in local context.
  405.            result.append(x)
  406.     # scan for no use
  407.     if not unused_ok:
  408.        for (name, set) in locals.items():
  409.            [where, count] = set
  410.            if count<1:
  411.               context.complain(
  412.                  "(%s) %s defined before %s not used" % (sname, `name`, where))
  413.     return result  
  414.  
  415. ### note, need to make special case for qualified names
  416. #@R powera :: power >> atom trailerlist
  417.  
  418. def powera(list, context):
  419.     #print "powera", list
  420.     [a, (t, full)] = list
  421.     if a and full:
  422.        # atom is a qualified name
  423.        (ln, ri, op, n) = a[0]
  424.        result = [ (ln, ri, "qref", n) ]
  425.     else:
  426.        result = a
  427.     result = result + t
  428.     #print "returning", result
  429.     return result
  430.        
  431. #@R trailerlist0 :: trailerlist >> 
  432. def trailerlist0(list, context):
  433.     return ([], 0) # empty trailerlist
  434.  
  435. #@R trailerlistn :: trailerlist >> trailer trailerlist
  436. def trailerlistn(list, context):
  437.     #print "trailerlistn", list
  438.     result = list[0] + list[1][0]
  439.     for i in xrange(len(result)):
  440.         (a, b, op, d) = result[i]
  441.         result[i] = (a, b, "qref", d)
  442.     return (result, 1)
  443.  
  444. # make name+parameters set local reduce suite...
  445.  
  446. def default_reduction(list, context):
  447.     # append all lists
  448.     from types import ListType
  449.     #print "defred", list
  450.     #return
  451.     result = []
  452.     for x in list:
  453.         if type(x)==ListType:
  454.            if result == []:
  455.               if len(x)>0 and type(x[0])==ListType:
  456.                  raise "oops", x
  457.               result = x
  458.            else:
  459.               for y in x:
  460.                   result.append(y)
  461.     return result
  462.  
  463. def aname(list, context):
  464.     #print "aname", list, context
  465.     L = context.LexD
  466.     # note -L.realindex makes rhs of assignment seem before lhs in sort.
  467.     return [ (L.lineno, -L.realindex, "ref", list[0]) ]
  468.  
  469.  
  470. # the highest level reduction!
  471. # all1 :: all >> file_input DEDENT
  472. def all1(list, context):
  473.     stuff = list[0]
  474.     context.when_done(stuff)
  475.  
  476. # first test
  477. def BindRules(pyg):
  478.     for name in pyg.RuleNameToIndex.keys():
  479.         pyg.Bind(name, default_reduction)
  480.     pyg.Bind("all1", all1)
  481.     pyg.Bind("testlambda1", testlambda1)
  482.     pyg.Bind("except2", except2)
  483.     pyg.Bind("namearg", namearg)
  484.     pyg.Bind("rfrom", rfrom)
  485.     pyg.Bind("rfromc", rfromc)
  486.     pyg.Bind("class1", class1)
  487.     pyg.Bind("class2", class2)
  488.     pyg.Bind("aname", aname)
  489.     pyg.Bind("assn1", assn1)
  490.     pyg.Bind("assnn", assnn)
  491.     pyg.Bind("assn1c", assn1c)
  492.     pyg.Bind("assn1c2", assn1c2)
  493.     pyg.Bind("assnnc", assnnc)
  494.     pyg.Bind("dn1", dn1)
  495.     pyg.Bind("nlistn", nlistn)
  496.     pyg.Bind("nlist1", nlist1)
  497.     pyg.Bind("global1", global1)
  498.     pyg.Bind("globaln", globaln)
  499.     pyg.Bind("for1", for1)
  500.     pyg.Bind("for2", for2)
  501.     pyg.Bind("powera", powera)
  502.     pyg.Bind("trailerlist0", trailerlist0)
  503.     pyg.Bind("trailerlistn", trailerlistn)
  504.     pyg.Bind("params1", params1)
  505.     pyg.Bind("params1c", params1c)
  506.     pyg.Bind("params2", params2)
  507.     pyg.Bind("params3", params3)
  508.     pyg.Bind("params4", params4)
  509.     pyg.Bind("argd", argd)
  510.     pyg.Bind("arg2", arg2)
  511.     pyg.Bind("arg3", arg3)
  512.     pyg.Bind("arg4", arg4)
  513.     pyg.Bind("fpdef1", fpdef1)
  514.     pyg.Bind("fpdef2", fpdef2)
  515. #    pyg.Bind("fpdef2c", fpdef2c)
  516.     pyg.Bind("fplist1" , fplist1 )
  517.     pyg.Bind("fplistn" , fplistn)
  518.     pyg.Bind("rdef" , rdef)
  519. #    pyg.Bind( , )
  520.  
  521. class globalContext:
  522.     def __init__(self, lexd):
  523.         self.deferred = []
  524.         self.LexD = lexd
  525.     def complain(self, str):
  526.         print str
  527.     def defer_globals(self, globals):
  528.         self.deferred[0:0] = globals
  529.     def when_done(self, list):
  530.         stuff = list + self.deferred + self.patch_globals()
  531.         globals = analyse_scope("<module global>", stuff, self)
  532.         seen = {}
  533.         for (ln, ri, op, name) in globals:
  534.             if not seen.has_key(name) and op!="set":
  535.                seen[name] = name
  536.                self.complain(
  537.       "%s: (%s) %s not defined in module?" % (ln, op, `name`))
  538.         self.deferred = [] # reset state.
  539.     def patch_globals(self):
  540.         # patch in global names
  541.         import __builtin__
  542.         names = dir(__builtin__)
  543.         list = names[:]
  544.         list2 = names[:]
  545.         for i in xrange(len(list)):
  546.             list[i] = (-2, -900, "set", names[i])
  547.             list2[i] = (-1, -900, "qref", names[i])
  548.         return list + list2
  549.  
  550. teststring = """
  551. class x(y,z):
  552.   '''
  553.      a doc string
  554.      blah
  555.   '''
  556.   def test(this, that):
  557.     w = that+this+x, n
  558.     x = 1
  559.     return w
  560. """
  561.  
  562. def go():
  563.     import sys
  564.     try:
  565.         file = sys.argv[1]
  566.     except IndexError:
  567.         print "required input file missing, defaulting to test string"
  568.         data = teststring
  569.     else:
  570.         data = open(file).read()
  571.     print "setup"
  572.     (pyg, context) = setup()
  573.     print "now parsing"
  574.     lint(data, pyg, context)
  575.  
  576. def setup():
  577.     global pyg, context
  578.     import pygram
  579.     pyg = pygram.unMarshalpygram()
  580.     BindRules(pyg)
  581.     context = globalContext(pyg.LexD)
  582.     return (pyg, context)
  583.  
  584. def lint(data, pygin=None, contextin=None):
  585.     if pygin is None: pygin = pyg
  586.     if contextin is None: contextin = context
  587.     pygin.DoParse1(data, contextin)
  588.  
  589. def lintdir(directory_name):
  590.     """lint all files recursively in directory"""
  591.     from find import find
  592.     print "\n\nrecursively linting %s\n\n" % directory_name
  593.     (pyg, context) = setup()
  594.     python_files = find("*.py", directory_name)
  595.     for x in python_files:
  596.         print "\n\n [ %s ]\n\n" % x
  597.         lint( open(x).read(), pyg, context )
  598.         print "\014"
  599.  
  600. if __name__=="__main__": go()
  601.